﻿using System;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Fluffy.WinForms
{
	/// <summary>
	/// Provides static methods to read system icons for both folders and files.
	/// </summary>
	/// <example>
	/// <code>IconReader.GetFileIcon("c:\\general.xls");</code>
	/// </example>
	public class IconReader
	{
		/// <summary>
		/// Options to specify the size of icons to return.
		/// </summary>
		public enum IconSize
		{
			/// <summary>
			/// Specify large icon - 32 pixels by 32 pixels.
			/// </summary>
			Large = 0,
			/// <summary>
			/// Specify small icon - 16 pixels by 16 pixels.
			/// </summary>
			Small = 1
		}

		/// <summary>
		/// Options to specify whether folders should be in the open or closed state.
		/// </summary>
		public enum FolderType
		{
			/// <summary>
			/// Specify open folder.
			/// </summary>
			Open = 0,
			/// <summary>
			/// Specify closed folder.
			/// </summary>
			Closed = 1
		}

		/// <summary>
		/// Returns an icon for a given file - indicated by the name parameter.
		/// </summary>
		/// <param name="name">Pathname for file.</param>
		/// <param name="size">Large or small</param>
		/// <param name="linkOverlay">Whether to include the link icon</param>
		/// <returns>System.Drawing.Icon</returns>
		public static ImageSource GetFileIcon(string name, IconSize size, bool linkOverlay)
		{
			Shell32.Shfileinfo shfi = new Shell32.Shfileinfo();
			uint flags = Shell32.ShgfiIcon | Shell32.ShgfiUsefileattributes;

			if (linkOverlay) flags += Shell32.ShgfiLinkoverlay;

			/* Check the size specified for return. */
			if (IconSize.Small == size)
			{
				flags += Shell32.ShgfiSmallicon;
			}
			else
			{
				flags += Shell32.ShgfiLargeicon;
			}

			Shell32.SHGetFileInfo(name,
				Shell32.FileAttributeNormal,
				ref shfi,
				(uint)Marshal.SizeOf(shfi),
				flags);

			// Copy (clone) the returned icon to a new object, thus allowing us to clean-up properly
			Icon icon = (Icon)Icon.FromHandle(shfi.hIcon).Clone();
			User32.DestroyIcon(shfi.hIcon);		// Cleanup
			return ImageSourceFrom(icon);
		}

		/// <summary>
		/// Used to access system folder icons.
		/// </summary>
		/// <param name="size">Specify large or small icons.</param>
		/// <param name="folderType">Specify open or closed FolderType.</param>
		/// <returns>System.Drawing.Icon</returns>
		public static ImageSource GetFolderIcon(IconSize size, FolderType folderType)
		{
			// Need to add size check, although errors generated at present!
			uint flags = Shell32.ShgfiIcon | Shell32.ShgfiUsefileattributes;

			if (FolderType.Open == folderType)
			{
				flags += Shell32.ShgfiOpenicon;
			}

			if (IconSize.Small == size)
			{
				flags += Shell32.ShgfiSmallicon;
			}
			else
			{
				flags += Shell32.ShgfiLargeicon;
			}

			// Get the folder icon
			Shell32.Shfileinfo shfi = new Shell32.Shfileinfo();
			Shell32.SHGetFileInfo(Environment.GetFolderPath(Environment.SpecialFolder.Windows),
				Shell32.FileAttributeDirectory,
				ref shfi,
				(uint)Marshal.SizeOf(shfi),
				flags);

			Icon.FromHandle(shfi.hIcon);	// Load the icon from an HICON handle

			// Now clone the icon, so that it can be successfully stored in an ImageList
			Icon icon = (Icon)Icon.FromHandle(shfi.hIcon).Clone();

			User32.DestroyIcon(shfi.hIcon);		// Cleanup
			return ImageSourceFrom(icon);
		}

		private static ImageSource ImageSourceFrom(Icon icon)
		{
			Bitmap bmp = icon.ToBitmap();

			MemoryStream strm = new MemoryStream();
			bmp.Save(strm, System.Drawing.Imaging.ImageFormat.Png);

			BitmapImage bmpImage = new BitmapImage();
			bmpImage.BeginInit();
			strm.Seek(0, SeekOrigin.Begin);
			bmpImage.StreamSource = strm;
			bmpImage.EndInit();

			return bmpImage;
		}  
	}


	/// <summary>
	/// Wraps necessary Shell32.dll structures and functions required to retrieve Icon Handles using SHGetFileInfo. Code
	/// courtesy of MSDN Cold Rooster Consulting case study.
	/// </summary>
	/// 

	// This code has been left largely untouched from that in the CRC example. The main changes have been moving
	// the icon reading code over to the IconReader type.
	public class Shell32
	{
		public const int MaxPath = 256;
		[StructLayout(LayoutKind.Sequential)]
		public struct Shitemid
		{
			public ushort cb;
			[MarshalAs(UnmanagedType.LPArray)]
			public byte[] abID;
		}

		[StructLayout(LayoutKind.Sequential)]
		public struct Itemidlist
		{
			public Shitemid mkid;
		}

		[StructLayout(LayoutKind.Sequential)]
		public struct Browseinfo
		{
			public IntPtr hwndOwner;
			public IntPtr pidlRoot;
			public IntPtr pszDisplayName;
			[MarshalAs(UnmanagedType.LPTStr)]
			public string lpszTitle;
			public uint ulFlags;
			public IntPtr lpfn;
			public int lParam;
			public IntPtr iImage;
		}

		// Browsing for directory.
		public const uint BifReturnonlyfsdirs = 0x0001;
		public const uint BifDontgobelowdomain = 0x0002;
		public const uint BifStatustext = 0x0004;
		public const uint BifReturnfsancestors = 0x0008;
		public const uint BifEditbox = 0x0010;
		public const uint BifValidate = 0x0020;
		public const uint BifNewdialogstyle = 0x0040;
		public const uint BifUsenewui = (BifNewdialogstyle | BifEditbox);
		public const uint BifBrowseincludeurls = 0x0080;
		public const uint BifBrowseforcomputer = 0x1000;
		public const uint BifBrowseforprinter = 0x2000;
		public const uint BifBrowseincludefiles = 0x4000;
		public const uint BifShareable = 0x8000;

		[StructLayout(LayoutKind.Sequential)]
		public struct Shfileinfo
		{
			public const int Namesize = 80;
			public IntPtr hIcon;
			public int iIcon;
			public uint dwAttributes;
			[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MaxPath)]
			public string szDisplayName;
			[MarshalAs(UnmanagedType.ByValTStr, SizeConst = Namesize)]
			public string szTypeName;
		};

		public const uint ShgfiIcon = 0x000000100;     // get icon
		public const uint ShgfiDisplayname = 0x000000200;     // get display name
		public const uint ShgfiTypename = 0x000000400;     // get type name
		public const uint ShgfiAttributes = 0x000000800;     // get attributes
		public const uint ShgfiIconlocation = 0x000001000;     // get icon location
		public const uint ShgfiExetype = 0x000002000;     // return exe type
		public const uint ShgfiSysiconindex = 0x000004000;     // get system icon index
		public const uint ShgfiLinkoverlay = 0x000008000;     // put a link overlay on icon
		public const uint ShgfiSelected = 0x000010000;     // show icon in selected state
		public const uint ShgfiAttrSpecified = 0x000020000;     // get only specified attributes
		public const uint ShgfiLargeicon = 0x000000000;     // get large icon
		public const uint ShgfiSmallicon = 0x000000001;     // get small icon
		public const uint ShgfiOpenicon = 0x000000002;     // get open icon
		public const uint ShgfiShelliconsize = 0x000000004;     // get shell size icon
		public const uint ShgfiPidl = 0x000000008;     // pszPath is a pidl
		public const uint ShgfiUsefileattributes = 0x000000010;     // use passed dwFileAttribute
		public const uint ShgfiAddoverlays = 0x000000020;     // apply the appropriate overlays
		public const uint ShgfiOverlayindex = 0x000000040;     // Get the index of the overlay

		public const uint FileAttributeDirectory = 0x00000010;
		public const uint FileAttributeNormal = 0x00000080;

		[DllImport("Shell32.dll")]
		public static extern IntPtr SHGetFileInfo(
			string pszPath,
			uint dwFileAttributes,
			ref Shfileinfo psfi,
			uint cbFileInfo,
			uint uFlags
			);
	}

	/// <summary>
	/// Wraps necessary functions imported from User32.dll. Code courtesy of MSDN Cold Rooster Consulting example.
	/// </summary>
	public class User32
	{
		/// <summary>
		/// Provides access to function required to delete handle. This method is used internally
		/// and is not required to be called separately.
		/// </summary>
		/// <param name="hIcon">Pointer to icon handle.</param>
		/// <returns>N/A</returns>
		[DllImport("User32.dll")]
		public static extern int DestroyIcon(IntPtr hIcon);
	}
}
